package com.emmmya.ocs.config.security.shiro;

import com.auth0.jwt.exceptions.TokenExpiredException;
import com.emmmya.ocs.modules.system.entity.Permissions;
import com.emmmya.ocs.modules.system.entity.Role;
import com.emmmya.ocs.modules.system.entity.User;
import com.emmmya.ocs.modules.system.entity.UserRole;
import com.emmmya.ocs.modules.system.service.LoginService;
import com.emmmya.ocs.modules.system.service.RoleService;
import com.emmmya.ocs.modules.system.service.UserRoleService;
import com.emmmya.ocs.common.constant.DataStatusConstant;
import com.emmmya.ocs.common.constant.RedisConstant;
import com.emmmya.ocs.common.utils.JwtUtils;
import com.emmmya.ocs.common.utils.ShiroUtil;
import com.emmmya.ocs.config.redis.RedisManager;
import com.emmmya.ocs.config.redis.ShiroRedisCacheManager;
import com.emmmya.ocs.config.security.jwt.JwtConfig;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.jpa.domain.Specification;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.util.List;
import java.util.stream.Collectors;


public class CustomRealm extends AuthorizingRealm {
    /**public void setName(){
     super.setName("MyRealm");
     }*/

    @Autowired
    private LoginService loginService;

    @Autowired
    private UserRoleService userRoleService;

    @Autowired
    private RoleService roleService;

    /**
     * shiro工具，给密码加盐
     */
    @Autowired
    private ShiroUtil shiroUtils;

    /**
     * jwtConfig jwt的配置基本方法类
     */
    @Autowired
    private JwtConfig jwtConfig;

    /**
     * jwt工具类---未完善
     */
    @Autowired
    private JwtUtils jwtUtils;

    /**
     * redis工具类
     */
    @Autowired
    private RedisManager redisManager;



    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) throws RuntimeException{
        //获取登录用户名  版本1
        //String name = (String) principalCollection.getPrimaryPrincipal();
//        String redisKeyUser = (String) principalCollection.getPrimaryPrincipal();
        User currentUser = (User) principalCollection.getPrimaryPrincipal();
        String redisKeyUser = RedisConstant.REDIS_USER_TOKEN + currentUser.getUsername();
        String token = (String)redisManager.get(redisKeyUser);
        Boolean over = jwtUtils.isOver(token);
        //判断token自身携带的过期时间
        if(over){
            throw new TokenExpiredException("token过期了,请重新登录！");
        }
        String username = jwtUtils.getUsername(token);
//        else{ 这个判断在jwtUtils.isOver(token)完成
//            //盘算redis存储还是否存在token
//            String o = (String) redisUtil.get(RedisConstant.REDIS_USER_TOKEN + username);
//            if(o == null){xception("token过期了,请重新登录！");
//            }
//        }
        ///String username = jwtConfig.getUsernameFromToken(token);
        //根据用户名去数据库查询用户信息
        User user = loginService.getUserByUsername(currentUser.getUsername());
        //添加角色和权限
//                throw new TokenExpiredE
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        if(user.getRoles() == null ||user.getRoles().isEmpty()){
            List<UserRole> userRoles = userRoleService.findByUserId(user.getId());
            List<Long> roleIds = userRoles.stream().map(e -> e.getRoleId()).collect(Collectors.toList());
            List<Role> roles = roleService.findAll(new Specification<Role>() {
                @Override
                public Predicate toPredicate(Root<Role> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
                    Predicate and = criteriaBuilder.and();
                    CriteriaBuilder.In<Long> id = criteriaBuilder.in(root.get("id").as(Long.class));
                    for (Long roleId : roleIds) {
                        id.value(roleId);
                    }
                    and = criteriaBuilder.and(and,id);
                    return and;
                }
            });
            user.setRoles(roles);
        }
        for (Role role : user.getRoles()) {
            //添加角色
            simpleAuthorizationInfo.addRole(role.getRoleName());
            //添加权限
            for (Permissions permissions : role.getPermissions()) {
                simpleAuthorizationInfo.addStringPermission(permissions.getPermissionCode());
            }
        }
        Session session = SecurityUtils.getSubject().getSession();
        session.setAttribute("roles", simpleAuthorizationInfo.getRoles());
        session.setAttribute("permissions", simpleAuthorizationInfo.getStringPermissions());
//         session.setAttribute("currentUser",user);
        return simpleAuthorizationInfo;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //3.1先把参数强转
        NoPwdUsernameToken upToken = (NoPwdUsernameToken) token;
        //3.2获取用户自己输入的用户名和密码
        String username = upToken.getUsername();
        //3.3这里获取的密码是字符数组。需要调用String的构造来转换一下
        String pwd = new String(upToken.getPassword());
        User user = loginService.getUserByUsername(username);
        if(user==null || user.getDelFlag().equals(DataStatusConstant.DEL_STATUS)){
            throw new AuthenticationException("用户不存在");
        }
        Boolean isAdmin = false;
        //判断角色
        for (Role role : user.getRoles()) {
            if("admin".equals(role.getRoleName())){
                isAdmin = true;
                break;
            }
        }
        if(!isAdmin && user.getDelFlag().equals(DataStatusConstant.DISABLE_STATUS)){
            throw new AuthenticationException("当前用户被禁用。请联系管理员处理！");
        }
        //3.3.1 给密码加盐 第一个参数是加密方式，第二参数是被加密的数据，第三个参数是盐值，第四个参数是加密次数
        String s = shiroUtils.encryptionToHex(pwd);

        //3.4查询数据库与数据库的密码对比一下是否相同。   账号密码方式登录
        if(s.equals(user.getPassword())) {
            //3.5
            //3.5.1生成用户token
            String jwtToken = jwtConfig.createToken(user.getUsername());
            redisManager.set(RedisConstant.REDIS_USER_TOKEN+username,jwtToken,jwtConfig.getExpire());
            //3.5.2 数据认证成功，把数据保存在SimpleAuthenticationInfo中并返回
            /**
             *参数一：安全数据，可以保存用户名，或者javabean(User)对象
             *参数二：密码
             *参数三：当前的realm域的名字
             */
            user.setPassword(null);
            SimpleAuthenticationInfo einfo = new SimpleAuthenticationInfo(user, pwd, this.getName());
            return einfo;
        }
        else{
            //3.6出错则抛出异常
            throw new AuthenticationException("账号或密码错误！！");
        }

    }

    /**
     * 重写方法,清除当前用户的的 授权缓存
     * @param principals
     */
    @Override
    public void clearCachedAuthorizationInfo(PrincipalCollection principals) {
        User currentUser = (User)principals.getPrimaryPrincipal();
        String authorizationCacheName = this.getAuthorizationCacheName();

        ShiroRedisCacheManager cacheManager = (ShiroRedisCacheManager)getCacheManager();

        super.clearCachedAuthorizationInfo(principals);
        String cacheKey = "shiro:cache:" + authorizationCacheName + ":" + currentUser.getUsername();
        Boolean del = cacheManager.getShiroRedisManager().del(cacheKey);
        System.out.println(del);

    }

    /**
     * 重写方法，清除当前用户的 认证缓存
     * @param principals
     */
    @Override
    public void clearCachedAuthenticationInfo(PrincipalCollection principals) {
        User currentUser = (User)principals.getPrimaryPrincipal();
        super.clearCachedAuthenticationInfo(principals);
        String authenticationCacheName = this.getAuthenticationCacheName();
        ShiroRedisCacheManager cacheManager = (ShiroRedisCacheManager)getCacheManager();
        String cacheKey = "shiro:cache:" + authenticationCacheName + ":" + currentUser.getUsername();
        Boolean del = cacheManager.getShiroRedisManager().del(cacheKey);
        System.out.println(del);
    }

    @Override
    public void clearCache(PrincipalCollection principals) {
        super.clearCache(principals);
    }

    /**
     * 自定义方法：清除所有 授权缓存
     */
    public void clearAllCachedAuthorizationInfo() {
        getAuthorizationCache().clear();
    }

    /**
     * 自定义方法：清除所有 认证缓存
     */
    public void clearAllCachedAuthenticationInfo() {
        getAuthenticationCache().clear();
    }

    /**
     * 自定义方法：清除所有的  认证缓存  和 授权缓存
     */
    public void clearAllCache() {
        clearAllCachedAuthenticationInfo();
        clearAllCachedAuthorizationInfo();
    }

}
